package manet;

import java.awt.Color;
import java.awt.Point;
import java.math.BigInteger;
import java.util.List;

import javaclient2.FiducialInterface;
import javaclient2.LaserInterface;
import javaclient2.PlayerClient;
import javaclient2.Position2DInterface;
import javaclient2.structures.PlayerConstants;
import javaclient2.structures.fiducial.PlayerFiducialData;
import javaclient2.structures.fiducial.PlayerFiducialItem;
import javaclient2.structures.laser.PlayerLaserData;


import manet.comm.Communicator;
import manet.crypto.CryptoGenerator;
import manet.crypto.GroupManager;
import manet.crypto.KeyManager;
import manet.gui.MapConstants;

/**
 * This class represents a singular node that wishes to communicate
 * with other nodes using a standardized cryptographic communication
 * protocol.
 * 
 * @author grin0077 stra0556
 *
 */

public class Node implements MovementConstants, MapConstants{

	/** PlayerStage Client Object */
	private PlayerClient robot = null;

	/** The interface to the 2D position  */
	Position2DInterface locationIntf = null;

	/** The fiducial interface */
	FiducialInterface fiducialIntf = null;

	/** The laser interface used for movement */
	LaserInterface laserIntf = null;

	/** instance Communicator */
	private Communicator comm = null;

	/** instance group manager */
	private GroupManager groupManager = null;

	/** instance key manager */
	private KeyManager keyManager = null;

	/** The server name of the remote robot */
	private String serverName;

	/** The structure used to store p, q, g as well as generate random numbers */
	CryptoGenerator cGen = null;
	
	/** The port number of the remote robot */
	int port;
	
	/** Group identifier used by the GUI to determine which robots are grouped */
	int groupID = 0;

	/** The big integer corresponding to the communication key */
	BigInteger keyValue = BigInteger.ZERO;

	/** Boolean representing movement state */
	boolean freeze = false;

	/** The center position of the robot */
	Point centerPosition = new Point(0,0);

	/**
	 * @param server The server where the player/stage is running
	 * @param port   The port to connect to the server 
	 * @param com    The communicator for the node 
	 */
	public Node(String server, int port, Communicator com, CryptoGenerator cGen) {
		this(server, port, com, null, null, cGen);
	}

	/**
	 * @param server The name of the player/stage server
	 * @param port   The port to connect to the server 
	 * @param com    The communicator for the node 
	 * @param groupMan The group manager for the node
	 * @param keyMan  The key manager for the node
	 */
	public Node(String server, int port, Communicator com, GroupManager groupMan, KeyManager keyMan, CryptoGenerator cGen) {
		try {
			/* Make a connection to the server  */
			robot = new PlayerClient(server, port);
			
			/*Obtain the position interface so we can draw the info later */
			locationIntf = robot.requestInterfacePosition2D( 0, PlayerConstants.PLAYER_OPEN_MODE);
			
			/* Obtain the fiducial interface to get our neighbors */
			fiducialIntf = robot.requestInterfaceFiducial( 0, PlayerConstants.PLAYER_OPEN_MODE);
			
			/* Obtain the laser interface to get obsticles */
			laserIntf = robot.requestInterfaceLaser(0, PlayerConstants.PLAYER_OPEN_MODE);
			
			/* Tell the server we prefer to pull our information than have it pushed */
			robot.requestDataDeliveryMode(PlayerConstants.PLAYER_DATAMODE_PULL);
		} catch (Exception e){
			System.err.println("Could not obtain position interface for robot " + robot+ " Exception: " + e.getMessage() );
		}
		comm = com;
		groupManager = groupMan;
		keyManager = keyMan;
		this.cGen = cGen;
		this.port = port;
		this.serverName = server;
	}

	/**
	 * @return The communicator for the robot 
	 */
	public Communicator getCommunicator() {
		return comm;
	}

	/** Toggles and returns new movement state */
	public boolean toggleFreeze() {
		return freeze = !freeze;
	}

	
	/**
	 * @return Return a copy of the cached center position 
	 */
	public Point getCoords(){
		return new Point(centerPosition);
	}

	/**
	 *  Method used to pull the updated coordinates from the server as well
	 *  as cache them for later use 
	 */
	private void updateCoords(){
		try {
			/* Generate the X coordinate to display give the location*/
			int XCoord = Math.round((locationIntf.getX() + ZERO_VALUE)*CONVERSION_FACTOR);
			
			/* Generate the Y coordinate to display give the location*/
			int YCoord = Math.round((locationIntf.getY()*-1 + ZERO_VALUE)*CONVERSION_FACTOR);
			
			/* Set the new center position */
			centerPosition = (new Point( XCoord, YCoord));
		} catch (Exception e){
			System.err.println("Could not use position interface for robot " + robot+ " Exception: " + e.getMessage() );
			centerPosition = new Point( 0, 0 );
		}
	}

	
	
	/**
	 * Method used to pull all the data from the server
	 */
	public void updateData(){
		robot.readAll();
		updateCoords();
		return;
	}

	
	/**
	 * @return The pretty color to be display corresponding to the group
	 */
	public Color getFillColor(){
		return new Color((groupID)*50%255, (groupID+2)*90%255, (groupID+1)*80%255);		
	}
	
	

	/**
	 * @return The groupID of the node 
	 */
	public int getGroupID() {
		return groupID;
	}

	
	
	/**
	 * @param groupID The new groupID
	 * @param nodes   The list of nodes so we can tell our neighbors to update as well
	 */
	public void setGroupID( int groupID, List<Node> nodes ){
		this.groupID = groupID;
		PlayerFiducialItem[] pfis = getFiducialItems();
		if (pfis == null) return;
		for( PlayerFiducialItem neighbor : getFiducialItems()){
			if( neighbor != null && nodes.get(neighbor.getId() - 1).getGroupID() == 0)
				nodes.get(neighbor.getId() - 1).setGroupID(getGroupID(), nodes);
		}
	}

	/**
	 * @return The key value for the group 
	 */
	public BigInteger getKey() {
		return keyValue;
	}
	
	/**
	 * Set the key for this node 
	 */
	public void setKey( BigInteger keyValue) {
		this.keyValue = keyValue;
	}
	

	
	/**
	 * @return The server name where the stage server is running 
	 */
	public String getServer(){
		return serverName;
	}

	/**
	 * @return The port number of the Stage server
	 */
	public int getPort(){
		return port;
	}
	
	/**
	 * Method to zeroize the group id prior to new group calculation 
	 */
	public void zeroize(){
		
		/* Set the group ID to zero for the next pass */
		this.groupID = 0;
	}

	/**
	 * @return returns the PlayerFiducialItem corresponding to the neighbors 
	 */
	public PlayerFiducialItem[] getFiducialItems(){
		PlayerFiducialData pfd = fiducialIntf.getData();
		return pfd == null ? null : pfd.getFiducials();
	}

	/**
	 * Method to generate a random movement for the robot 
	 */
	public void randomMove(){
		/* Make sure we shouldn't freeze our movement */
		if( !freeze ) {

			/* Get the object detection information from the interface */
			PlayerLaserData data = laserIntf.getData();
			if( data == null ){
				return;
			}
			/* Loop through all the objects detected to make sure none is too close */
			for( float range : data.getRanges()){

				/* We have an object too close */
				if( range < Node.MIN_DISTANCE ){

					/* Set the speed and turning parameters to evasive mode */
					locationIntf.setSpeed(Node.EVASIVE_SPEED, Node.EVASIVE_TURN);
					return;
				}
			}

			/* No objects are too close, so full speed ahead at a random heading */
			locationIntf.setSpeed( Node.FULL_SPEED, (float) Math.random() / 5 );
		} else {
			/* Out movement if to be frozen so set the speed to nothing  */
			locationIntf.setSpeed( 0, 0);
		}
	}

	/**
	 * 
	 */
	public int getRange() {
		// TODO get from Fiducial Interface
		return ROBOT_RANGE* CONVERSION_FACTOR;
	}

	/**
	 * @return
	 */
	public Point getUpperLeft() {
		Point center = getCoords();
		int radius = getRange();
		return new Point( center.x - radius, center.y - radius );
	}	
}
